/*
 * @(#)MetalFileChooserUI.java	1.69 03/02/17
 *
 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package lookandfeel.Dmetal;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.EventObject;
import java.util.Locale;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.KeyStroke;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileSystemView;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicFileChooserUI;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.text.Position;

import sun.awt.shell.ShellFolder;

/**
 * Metal L&F implementation of a FileChooser.
 * 
 * @version 1.69 02/17/03
 * @author Jeff Dinkins
 */
public class MetalFileChooserUI extends BasicFileChooserUI
{

	// Much of the Metal UI for JFilechooser is just a copy of
	// the windows implementation, but using Metal themed buttons, lists,
	// icons, etc. We are planning a complete rewrite, and hence we've
	// made most things in this class private.
	private JPanel centerPanel;

	private JLabel lookInLabel;

	private JComboBox directoryComboBox;

	private DirectoryComboBoxModel directoryComboBoxModel;

	private Action directoryComboBoxAction = new DirectoryComboBoxAction();

	private FilterComboBoxModel filterComboBoxModel;

	private JTextField fileNameTextField;

	private JToggleButton listViewButton;

	private JToggleButton detailsViewButton;

	private JPanel listViewPanel;

	private JPanel detailsViewPanel;

	private JPanel currentViewPanel;

	private FocusListener editorFocusListener = new FocusAdapter()
	{
		@Override
		public void focusLost(FocusEvent e)
		{
			if (!e.isTemporary()) applyEdit();
		}
	};

	private boolean useShellFolder;

	private ListSelectionModel listSelectionModel;

	private JList list;

	private JTable detailsTable;

	private JButton approveButton;

	private JButton cancelButton;

	private JPanel buttonPanel;

	private JPanel bottomPanel;

	private JComboBox filterComboBox;

	private static final Dimension hstrut5 = new Dimension(5, 1);

	@SuppressWarnings( "unused" )
	private static final Dimension hstrut11 = new Dimension(11, 1);

	private static final Dimension vstrut5 = new Dimension(1, 5);

	private static final Insets shrinkwrap = new Insets(0, 0, 0, 0);

	// Preferred and Minimum sizes for the dialog box
	private static int PREF_WIDTH = 500;

	private static int PREF_HEIGHT = 326;

	private static Dimension PREF_SIZE = new Dimension(PREF_WIDTH, PREF_HEIGHT);

	private static int MIN_WIDTH = 500;

	private static int MIN_HEIGHT = 326;

	private static Dimension MIN_SIZE = new Dimension(MIN_WIDTH, MIN_HEIGHT);

	private static int LIST_PREF_WIDTH = 405;

	private static int LIST_PREF_HEIGHT = 135;

	private static Dimension LIST_PREF_SIZE = new Dimension(LIST_PREF_WIDTH,
			LIST_PREF_HEIGHT);

	private static final int COLUMN_FILENAME = 0;

	private static final int COLUMN_FILESIZE = 1;

	private static final int COLUMN_FILETYPE = 2;

	private static final int COLUMN_FILEDATE = 3;

	private static final int COLUMN_FILEATTR = 4;

	private static final int COLUMN_COLCOUNT = 5;

	private int[] COLUMN_WIDTHS =
	{ 150, 75, 130, 130, 40 };

	// Labels, mnemonics, and tooltips (oh my!)
	private int lookInLabelMnemonic = 0;

	private String lookInLabelText = null;

	private String saveInLabelText = null;

	private int fileNameLabelMnemonic = 0;

	private String fileNameLabelText = null;

	private int filesOfTypeLabelMnemonic = 0;

	private String filesOfTypeLabelText = null;

	private String upFolderToolTipText = null;

	private String upFolderAccessibleName = null;

	private String homeFolderToolTipText = null;

	private String homeFolderAccessibleName = null;

	private String newFolderToolTipText = null;

	private String newFolderAccessibleName = null;

	private String listViewButtonToolTipText = null;

	private String listViewButtonAccessibleName = null;

	private String detailsViewButtonToolTipText = null;

	private String detailsViewButtonAccessibleName = null;

	private String fileNameHeaderText = null;

	private String fileSizeHeaderText = null;

	private String fileTypeHeaderText = null;

	private String fileDateHeaderText = null;

	private String fileAttrHeaderText = null;

	//
	// ComponentUI Interface Implementation methods
	//
	public static ComponentUI createUI(JComponent c)
	{
		return new MetalFileChooserUI((JFileChooser) c);
	}

	public MetalFileChooserUI( JFileChooser filechooser )
	{
		super(filechooser);
	}

	@Override
	public void installUI(JComponent c)
	{
		super.installUI(c);
	}

	@Override
	public void uninstallComponents(JFileChooser fc)
	{
		fc.removeAll();
		bottomPanel = null;
		buttonPanel = null;
	}

	@Override
	public void installComponents(JFileChooser fc)
	{
		FileSystemView fsv = fc.getFileSystemView();

		fc.setBorder(new EmptyBorder(12, 12, 11, 11));
		fc.setLayout(new BorderLayout(0, 11));

		// ********************************* //
		// **** Construct the top panel **** //
		// ********************************* //

		// Directory manipulation buttons
		JPanel topPanel = new JPanel(new BorderLayout(11, 0));
		JPanel topButtonPanel = new JPanel();
		topButtonPanel.setLayout(new BoxLayout(topButtonPanel,
				BoxLayout.LINE_AXIS));
		topPanel.add(topButtonPanel, BorderLayout.AFTER_LINE_ENDS);

		// Add the top panel to the fileChooser
		fc.add(topPanel, BorderLayout.NORTH);

		// ComboBox Label
		lookInLabel = new JLabel(lookInLabelText);
		lookInLabel.setDisplayedMnemonic(lookInLabelMnemonic);
		topPanel.add(lookInLabel, BorderLayout.BEFORE_LINE_BEGINS);

		// CurrentDir ComboBox
		directoryComboBox = new JComboBox();
		directoryComboBox.getAccessibleContext().setAccessibleDescription(
				lookInLabelText);
		directoryComboBox.putClientProperty(
				"JComboBox.lightweightKeyboardNavigation", "Lightweight");
		lookInLabel.setLabelFor(directoryComboBox);
		directoryComboBoxModel = createDirectoryComboBoxModel(fc);
		directoryComboBox.setModel(directoryComboBoxModel);
		directoryComboBox.addActionListener(directoryComboBoxAction);
		directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));
		directoryComboBox.setAlignmentX(Component.LEFT_ALIGNMENT);
		directoryComboBox.setAlignmentY(Component.TOP_ALIGNMENT);
		directoryComboBox.setMaximumRowCount(8);

		topPanel.add(directoryComboBox, BorderLayout.CENTER);

		// Up Button
		JButton upFolderButton = new JButton(getChangeToParentDirectoryAction());
		upFolderButton.setText(null);
		upFolderButton.setIcon(upFolderIcon);
		upFolderButton.setToolTipText(upFolderToolTipText);
		upFolderButton.getAccessibleContext().setAccessibleName(
				upFolderAccessibleName);
		upFolderButton.setAlignmentX(Component.LEFT_ALIGNMENT);
		upFolderButton.setAlignmentY(Component.CENTER_ALIGNMENT);
		upFolderButton.setMargin(shrinkwrap);

		topButtonPanel.add(upFolderButton);
		topButtonPanel.add(Box.createRigidArea(hstrut5));

		// Home Button
		File homeDir = fsv.getHomeDirectory();
		String toolTipText = homeFolderToolTipText;
		if (fsv.isRoot(homeDir))
			toolTipText = getFileView(fc).getName(homeDir); // Probably
		// "Desktop".

		JButton b = new JButton(homeFolderIcon);
		b.setToolTipText(toolTipText);
		b.getAccessibleContext().setAccessibleName(homeFolderAccessibleName);
		b.setAlignmentX(Component.LEFT_ALIGNMENT);
		b.setAlignmentY(Component.CENTER_ALIGNMENT);
		b.setMargin(shrinkwrap);

		b.addActionListener(getGoHomeAction());
		topButtonPanel.add(b);
		topButtonPanel.add(Box.createRigidArea(hstrut5));

		// New Directory Button
		b = new JButton(getNewFolderAction());
		b.setText(null);
		b.setIcon(newFolderIcon);
		b.setToolTipText(newFolderToolTipText);
		b.getAccessibleContext().setAccessibleName(newFolderAccessibleName);
		b.setAlignmentX(Component.LEFT_ALIGNMENT);
		b.setAlignmentY(Component.CENTER_ALIGNMENT);
		b.setMargin(shrinkwrap);

		topButtonPanel.add(b);
		topButtonPanel.add(Box.createRigidArea(hstrut5));

		// View button group
		ButtonGroup viewButtonGroup = new ButtonGroup();

		class ViewButtonListener implements ActionListener
		{
			JFileChooser fc;

			ViewButtonListener( JFileChooser fc )
			{
				this.fc = fc;
			}

			public void actionPerformed(ActionEvent e)
			{
				JToggleButton b = (JToggleButton) e.getSource();
				JPanel oldViewPanel = currentViewPanel;

				if (b == detailsViewButton)
				{
					if (detailsViewPanel == null)
					{
						detailsViewPanel = createDetailsView(fc);
						detailsViewPanel.setPreferredSize(LIST_PREF_SIZE);
					}
					currentViewPanel = detailsViewPanel;
				}
				else currentViewPanel = listViewPanel;
				if (currentViewPanel != oldViewPanel)
				{
					centerPanel.remove(oldViewPanel);
					centerPanel.add(currentViewPanel, BorderLayout.CENTER);
					centerPanel.revalidate();
					centerPanel.repaint();
				}
			}
		}

		ViewButtonListener viewButtonListener = new ViewButtonListener(fc);

		// List Button
		listViewButton = new JToggleButton(listViewIcon);
		listViewButton.setToolTipText(listViewButtonToolTipText);
		listViewButton.getAccessibleContext().setAccessibleName(
				listViewButtonAccessibleName);
		listViewButton.setSelected(true);
		listViewButton.setAlignmentX(Component.LEFT_ALIGNMENT);
		listViewButton.setAlignmentY(Component.CENTER_ALIGNMENT);
		listViewButton.setMargin(shrinkwrap);
		listViewButton.addActionListener(viewButtonListener);
		topButtonPanel.add(listViewButton);
		viewButtonGroup.add(listViewButton);

		// Details Button
		detailsViewButton = new JToggleButton(detailsViewIcon);
		detailsViewButton.setToolTipText(detailsViewButtonToolTipText);
		detailsViewButton.getAccessibleContext().setAccessibleName(
				detailsViewButtonAccessibleName);
		detailsViewButton.setAlignmentX(Component.LEFT_ALIGNMENT);
		detailsViewButton.setAlignmentY(Component.CENTER_ALIGNMENT);
		detailsViewButton.setMargin(shrinkwrap);
		detailsViewButton.addActionListener(viewButtonListener);
		topButtonPanel.add(detailsViewButton);
		viewButtonGroup.add(detailsViewButton);

		// Use ShellFolder class to populate combobox only if
		// FileSystemView.getRoots() returns one folder and that is
		// the same as the first item in the ShellFolder combobox list.
		{
			useShellFolder = false;
			File[] roots = fsv.getRoots();
			if (( roots != null ) && ( roots.length == 1 ))
			{
				File[] cbFolders = (File[]) ShellFolder
						.get("fileChooserComboBoxFolders");
				if (( cbFolders != null ) && ( cbFolders.length > 0 )
						&& ( roots[0] == cbFolders[0] )) useShellFolder = true;
			}
		}

		// ************************************** //
		// ******* Add the directory pane ******* //
		// ************************************** //
		centerPanel = new JPanel(new BorderLayout());
		listViewPanel = createList(fc);
		listSelectionModel = list.getSelectionModel();
		listViewPanel.setPreferredSize(LIST_PREF_SIZE);
		centerPanel.add(listViewPanel, BorderLayout.CENTER);
		currentViewPanel = listViewPanel;
		centerPanel.add(getAccessoryPanel(), BorderLayout.AFTER_LINE_ENDS);
		JComponent accessory = fc.getAccessory();
		if (accessory != null) getAccessoryPanel().add(accessory);
		fc.add(centerPanel, BorderLayout.CENTER);

		// ********************************** //
		// **** Construct the bottom panel ** //
		// ********************************** //
		JPanel bottomPanel = getBottomPanel();
		bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS));
		fc.add(bottomPanel, BorderLayout.SOUTH);

		// FileName label and textfield
		JPanel fileNamePanel = new JPanel();
		fileNamePanel.setLayout(new BoxLayout(fileNamePanel,
				BoxLayout.LINE_AXIS));
		bottomPanel.add(fileNamePanel);
		bottomPanel.add(Box.createRigidArea(vstrut5));

		AlignedLabel fileNameLabel = new AlignedLabel(fileNameLabelText);
		fileNameLabel.setDisplayedMnemonic(fileNameLabelMnemonic);
		fileNamePanel.add(fileNameLabel);

		fileNameTextField = new JTextField(35)
		{
			/**
			 * 
			 */
			private static final long serialVersionUID = -2477853106367196538L;

			@Override
			public Dimension getMaximumSize()
			{
				return new Dimension(Short.MAX_VALUE,
						super.getPreferredSize().height);
			}
		};
		fileNamePanel.add(fileNameTextField);
		fileNameLabel.setLabelFor(fileNameTextField);
		fileNameTextField.addFocusListener(new FocusAdapter()
		{
			@Override
			public void focusGained(FocusEvent e)
			{
				if (!getFileChooser().isMultiSelectionEnabled())
					listSelectionModel.clearSelection();
			}
		});
		if (fc.isMultiSelectionEnabled())
			setFileName(fileNameString(fc.getSelectedFiles()));
		else setFileName(fileNameString(fc.getSelectedFile()));

		// Filetype label and combobox
		JPanel filesOfTypePanel = new JPanel();
		filesOfTypePanel.setLayout(new BoxLayout(filesOfTypePanel,
				BoxLayout.LINE_AXIS));
		bottomPanel.add(filesOfTypePanel);

		AlignedLabel filesOfTypeLabel = new AlignedLabel(filesOfTypeLabelText);
		filesOfTypeLabel.setDisplayedMnemonic(filesOfTypeLabelMnemonic);
		filesOfTypePanel.add(filesOfTypeLabel);

		filterComboBoxModel = createFilterComboBoxModel();
		fc.addPropertyChangeListener(filterComboBoxModel);
		filterComboBox = new JComboBox(filterComboBoxModel);
		filterComboBox.getAccessibleContext().setAccessibleDescription(
				filesOfTypeLabelText);
		filesOfTypeLabel.setLabelFor(filterComboBox);
		filterComboBox.setRenderer(createFilterComboBoxRenderer());
		filesOfTypePanel.add(filterComboBox);

		// buttons
		getButtonPanel().setLayout(new ButtonAreaLayout());

		approveButton = new JButton(getApproveButtonText(fc));
		// Note: Metal does not use mnemonics for approve and cancel
		approveButton.addActionListener(getApproveSelectionAction());
		approveButton.setToolTipText(getApproveButtonToolTipText(fc));
		getButtonPanel().add(approveButton);

		cancelButton = new JButton(cancelButtonText);
		cancelButton.setToolTipText(cancelButtonToolTipText);
		cancelButton.addActionListener(getCancelSelectionAction());
		getButtonPanel().add(cancelButton);

		if (fc.getControlButtonsAreShown()) addControlButtons();

		groupLabels(new AlignedLabel[]
		{ fileNameLabel, filesOfTypeLabel });
	}

	protected JPanel getButtonPanel()
	{
		if (buttonPanel == null) buttonPanel = new JPanel();
		return buttonPanel;
	}

	protected JPanel getBottomPanel()
	{
		if (bottomPanel == null) bottomPanel = new JPanel();
		return bottomPanel;
	}

	@Override
	protected void installStrings(JFileChooser fc)
	{
		super.installStrings(fc);

		Locale l = fc.getLocale();

		lookInLabelMnemonic = UIManager
				.getInt("FileChooser.lookInLabelMnemonic");
		lookInLabelText = UIManager.getString("FileChooser.lookInLabelText", l);
		saveInLabelText = UIManager.getString("FileChooser.saveInLabelText", l);

		fileNameLabelMnemonic = UIManager
				.getInt("FileChooser.fileNameLabelMnemonic");
		fileNameLabelText = UIManager.getString(
				"FileChooser.fileNameLabelText", l);

		filesOfTypeLabelMnemonic = UIManager
				.getInt("FileChooser.filesOfTypeLabelMnemonic");
		filesOfTypeLabelText = UIManager.getString(
				"FileChooser.filesOfTypeLabelText", l);

		upFolderToolTipText = UIManager.getString(
				"FileChooser.upFolderToolTipText", l);
		upFolderAccessibleName = UIManager.getString(
				"FileChooser.upFolderAccessibleName", l);

		homeFolderToolTipText = UIManager.getString(
				"FileChooser.homeFolderToolTipText", l);
		homeFolderAccessibleName = UIManager.getString(
				"FileChooser.homeFolderAccessibleName", l);

		newFolderToolTipText = UIManager.getString(
				"FileChooser.newFolderToolTipText", l);
		newFolderAccessibleName = UIManager.getString(
				"FileChooser.newFolderAccessibleName", l);

		listViewButtonToolTipText = UIManager.getString(
				"FileChooser.listViewButtonToolTipText", l);
		listViewButtonAccessibleName = UIManager.getString(
				"FileChooser.listViewButtonAccessibleName", l);

		detailsViewButtonToolTipText = UIManager.getString(
				"FileChooser.detailsViewButtonToolTipText", l);
		detailsViewButtonAccessibleName = UIManager.getString(
				"FileChooser.detailsViewButtonAccessibleName", l);

		fileNameHeaderText = UIManager.getString(
				"FileChooser.fileNameHeaderText", l);
		fileSizeHeaderText = UIManager.getString(
				"FileChooser.fileSizeHeaderText", l);
		fileTypeHeaderText = UIManager.getString(
				"FileChooser.fileTypeHeaderText", l);
		fileDateHeaderText = UIManager.getString(
				"FileChooser.fileDateHeaderText", l);
		fileAttrHeaderText = UIManager.getString(
				"FileChooser.fileAttrHeaderText", l);
	}

	@Override
	protected void installListeners(JFileChooser fc)
	{
		super.installListeners(fc);
		ActionMap actionMap = getActionMap();
		SwingUtilities.replaceUIActionMap(fc, actionMap);
	}

	@SuppressWarnings("all")
	protected ActionMap getActionMap()
	{
		return createActionMap();
	}

	@SuppressWarnings("all")
	protected ActionMap createActionMap()
	{
		AbstractAction escAction = new AbstractAction()
		{
			/**
			 * 
			 */
			private static final long serialVersionUID = -7184952728175562724L;

			public void actionPerformed(ActionEvent e)
			{
				if (editFile != null)
					cancelEdit();
				else getFileChooser().cancelSelection();
			}

			@Override
			public boolean isEnabled()
			{
				return getFileChooser().isEnabled();
			}
		};
		ActionMap map = new ActionMapUIResource();
		map.put("approveSelection", getApproveSelectionAction());
		map.put("cancelSelection", escAction);
		map.put("Go Up", getChangeToParentDirectoryAction());
		return map;
	}

	protected JPanel createList(JFileChooser fc)
	{
		JPanel p = new JPanel(new BorderLayout());
		final JFileChooser fileChooser = fc;
		list = new JList()
		{
			private static final long serialVersionUID = 3116253919223929906L;

			@Override
			public int getNextMatch(String prefix, int startIndex,
					Position.Bias bias)
			{
				ListModel model = getModel();
				int max = model.getSize();
				if (( prefix == null ) || ( startIndex < 0 )
						|| ( startIndex >= max ))
					throw new IllegalArgumentException();
				// start search from the next element before/after the selected
				// element
				boolean backwards = ( bias == Position.Bias.Backward );
				for (int i = startIndex; backwards ? i >= 0 : i < max; i += ( backwards ? -1
						: 1 ))
				{
					String filename = fileChooser.getName((File) model
							.getElementAt(i));
					if (filename.regionMatches(true, 0, prefix, 0, prefix
							.length())) return i;
				}
				return -1;
			}
		};
		list.setCellRenderer(new FileRenderer());
		list.setLayoutOrientation(JList.VERTICAL_WRAP);
		list.setVisibleRowCount(-1);

		if (fc.isMultiSelectionEnabled())
			list
					.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
		else list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		list.setModel(getModel());
		list.addListSelectionListener(createListSelectionListener(fc));
		list.addMouseListener(createDoubleClickListener(fc, list));
		list.addMouseListener(createSingleClickListener(fc, list));
		getModel().addListDataListener(new ListDataListener()
		{
			public void contentsChanged(ListDataEvent e)
			{
				// Update the selection after JList has been updated
				new DelayedSelectionUpdater();
			}

			public void intervalAdded(ListDataEvent e)
			{
				new DelayedSelectionUpdater();
			}

			public void intervalRemoved(ListDataEvent e)
			{
			}
		});

		JScrollPane scrollpane = new JScrollPane(list);
		p.add(scrollpane, BorderLayout.CENTER);
		return p;
	}

	class DetailsTableModel extends AbstractTableModel implements
			ListDataListener
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = -623328729267225201L;

		String[] columnNames =
		{ fileNameHeaderText, fileSizeHeaderText, fileTypeHeaderText,
				fileDateHeaderText, fileAttrHeaderText };

		JFileChooser chooser;

		ListModel listModel;

		DetailsTableModel( JFileChooser fc )
		{
			this.chooser = fc;
			listModel = getModel();
			listModel.addListDataListener(this);
		}

		public int getRowCount()
		{
			return listModel.getSize();
		}

		public int getColumnCount()
		{
			return COLUMN_COLCOUNT;
		}

		@Override
		public String getColumnName(int column)
		{
			return columnNames[column];
		}

		@Override
		public Class<?> getColumnClass(int column)
		{
			switch (column)
			{
				case COLUMN_FILENAME:
					return File.class;
				case COLUMN_FILEDATE:
					return Date.class;
				default:
					return super.getColumnClass(column);
			}
		}

		public Object getValueAt(int row, int col)
		{
			// Note: It is very important to avoid getting info on drives, as
			// this will trigger "No disk in A:" and similar dialogs.
			//
			// Use (f.exists() &&
			// !chooser.getFileSystemView().isFileSystemRoot(f)) to
			// determine if it is safe to call methods directly on f.

			File f = (File) listModel.getElementAt(row);
			switch (col)
			{
				case COLUMN_FILENAME:
					return f;

				case COLUMN_FILESIZE:
					if (!f.exists() || f.isDirectory()) return null;
					long len = f.length() / 1024L;
					if (len < 1024L)
						return ( ( len == 0L ) ? 1L : len ) + " KB";
					else
					{
						len /= 1024L;
						if (len < 1024L)
							return len + " MB";
						else
						{
							len /= 1024L;
							return len + " GB";
						}
					}

				case COLUMN_FILETYPE:
					if (!f.exists()) return null;
					return chooser.getFileSystemView()
							.getSystemTypeDescription(f);

				case COLUMN_FILEDATE:
					if (!f.exists()
							|| chooser.getFileSystemView().isFileSystemRoot(f))
						return null;
					long time = f.lastModified();
					return ( time == 0L ) ? null : new Date(time);

				case COLUMN_FILEATTR:
					if (!f.exists()
							|| chooser.getFileSystemView().isFileSystemRoot(f))
						return null;
					String attributes = "";
					if (!f.canWrite()) attributes += "R";
					if (f.isHidden()) attributes += "H";
					return attributes;
			}
			return null;
		}

		@Override
		public void setValueAt(Object value, int row, int col)
		{
			if (col == COLUMN_FILENAME)
			{
				JFileChooser chooser = getFileChooser();
				File f = (File) getValueAt(row, col);
				if (f != null)
				{
					String oldDisplayName = chooser.getName(f);
					String oldFileName = f.getName();
					String newDisplayName = ( (String) value ).trim();
					String newFileName;

					if (!newDisplayName.equals(oldDisplayName))
					{
						newFileName = newDisplayName;
						// Check if extension is hidden from user
						int i1 = oldFileName.length();
						int i2 = oldDisplayName.length();
						if (( i1 > i2 ) && ( oldFileName.charAt(i2) == '.' ))
							newFileName = newDisplayName
									+ oldFileName.substring(i2);

						// rename
						FileSystemView fsv = chooser.getFileSystemView();
						File f2 = fsv.createFileObject(f.getParentFile(),
								newFileName);
						if (!f2.exists()
								&& MetalFileChooserUI.this.getModel()
										.renameFile(f, f2))
						{
							if (fsv.isParent(chooser.getCurrentDirectory(), f2))
							{
								if (chooser.isMultiSelectionEnabled())
									chooser.setSelectedFiles(new File[]
									{ f2 });
								else chooser.setSelectedFile(f2);
							}
							else
							{
								// Could be because of delay in updating Desktop
								// folder
								// chooser.setSelectedFile(null);
							}
						}
						else
						{
							// PENDING(jeff) - show a dialog indicating failure
						}
					}
				}
			}
		}

		@Override
		public boolean isCellEditable(int row, int column)
		{
			return ( column == COLUMN_FILENAME );
		}

		public void contentsChanged(ListDataEvent e)
		{
			fireTableDataChanged();
		}

		public void intervalAdded(ListDataEvent e)
		{
			fireTableDataChanged();
		}

		public void intervalRemoved(ListDataEvent e)
		{
			fireTableDataChanged();
		}
	}

	class DetailsTableCellRenderer extends DefaultTableCellRenderer
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = -2006144244525181719L;

		JFileChooser chooser;

		DateFormat df;

		DetailsTableCellRenderer( JFileChooser chooser )
		{
			this.chooser = chooser;
			df = DateFormat.getDateTimeInstance(DateFormat.SHORT,
					DateFormat.SHORT, chooser.getLocale());
		}

		@Override
		public Component getTableCellRendererComponent(JTable table,
				Object value, boolean isSelected, boolean hasFocus, int row,
				int column)
		{

			if (( column == COLUMN_FILESIZE ) || ( column == COLUMN_FILEATTR ))
				setHorizontalAlignment(SwingConstants.TRAILING);
			else setHorizontalAlignment(SwingConstants.LEADING);

			return super.getTableCellRendererComponent(table, value,
					isSelected, hasFocus, row, column);
		}

		@Override
		public void setValue(Object value)
		{
			setIcon(null);
			if (value instanceof File)
			{
				File file = (File) value;
				String fileName = chooser.getName(file);
				setText(fileName);
				Icon icon = chooser.getIcon(file);
				setIcon(icon);
			}
			else if (value instanceof Date)
				setText(( value == null ) ? "" : df.format((Date) value));
			else super.setValue(value);
		}
	}

	protected JPanel createDetailsView(JFileChooser fc)
	{
		final JFileChooser chooser = fc;
		JPanel p = new JPanel(new BorderLayout());

		DetailsTableModel detailsTableModel = new DetailsTableModel(chooser);

		detailsTable = new JTable(detailsTableModel)
		{
			/**
			 * 
			 */
			private static final long serialVersionUID = -6032732196114025798L;

			// Handle Escape key events here
			@Override
			protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
					int condition, boolean pressed)
			{
				if (( e.getKeyCode() == KeyEvent.VK_ESCAPE )
						&& ( getCellEditor() == null ))
				{
					// We are not editing, forward to filechooser.
					chooser.dispatchEvent(e);
					return true;
				}
				return super.processKeyBinding(ks, e, condition, pressed);
			}
		};

		detailsTable.setComponentOrientation(chooser.getComponentOrientation());
		detailsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		detailsTable.setShowGrid(false);
		detailsTable.setSelectionModel(listSelectionModel);
		detailsTable.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);

		Font font = detailsTable.getFont();
		detailsTable.setRowHeight(Math.max(font.getSize(), 19) + 3);

		TableColumnModel columnModel = detailsTable.getColumnModel();
		TableColumn[] columns = new TableColumn[COLUMN_COLCOUNT];

		for (int i = 0; i < COLUMN_COLCOUNT; i++)
		{
			columns[i] = columnModel.getColumn(i);
			columns[i].setPreferredWidth(COLUMN_WIDTHS[i]);
		}

		if (!System.getProperty("os.name").startsWith("Windows"))
		{
			columnModel.removeColumn(columns[COLUMN_FILETYPE]);
			columnModel.removeColumn(columns[COLUMN_FILEATTR]);
		}

		TableCellRenderer cellRenderer = new DetailsTableCellRenderer(chooser);
		detailsTable.setDefaultRenderer(File.class, cellRenderer);
		detailsTable.setDefaultRenderer(Date.class, cellRenderer);
		detailsTable.setDefaultRenderer(Object.class, cellRenderer);

		// Install cell editor for editing file name
		final JTextField tf = new JTextField();
		tf.addFocusListener(editorFocusListener);
		columns[COLUMN_FILENAME].setCellEditor(new DefaultCellEditor(tf)
		{
			/**
			 * 
			 */
			private static final long serialVersionUID = -3632548736960396740L;

			@Override
			public boolean isCellEditable(EventObject e)
			{
				if (e instanceof MouseEvent)
				{
					MouseEvent me = (MouseEvent) e;
					int index = detailsTable.rowAtPoint(me.getPoint());
					return ( ( me.getClickCount() == 1 ) && detailsTable
							.isRowSelected(index) );
				}
				return super.isCellEditable(e);
			}

			@Override
			public Component getTableCellEditorComponent(JTable table,
					Object value, boolean isSelected, int row, int column)
			{
				Component comp = super.getTableCellEditorComponent(table,
						value, isSelected, row, column);
				if (value instanceof File)
				{
					tf.setText(chooser.getName((File) value));
					tf.requestFocus();
					tf.selectAll();
				}
				return comp;
			}
		});

		JList fakeList = new JList(detailsTableModel.listModel)
		{
			/**
			 * 
			 */
			private static final long serialVersionUID = 8455460667887175146L;

			JTable table = detailsTable;

			@Override
			public int locationToIndex(Point location)
			{
				return table.rowAtPoint(location);
			}

			@Override
			public Rectangle getCellBounds(int index0, int index1)
			{
				Rectangle r0 = table
						.getCellRect(index0, COLUMN_FILENAME, false);
				Rectangle r1 = table
						.getCellRect(index1, COLUMN_FILENAME, false);
				return r0.union(r1);
			}

			@Override
			public Object getSelectedValue()
			{
				return table
						.getValueAt(table.getSelectedRow(), COLUMN_FILENAME);
			}

			@Override
			public Component add(Component comp)
			{
				if (comp instanceof JTextField)
					return table.add(comp);
				else return super.add(comp);
			}

			@Override
			public void repaint()
			{
				if (table != null) table.repaint();
			}

			@Override
			public TransferHandler getTransferHandler()
			{
				if (table != null)
					return table.getTransferHandler();
				else return super.getTransferHandler();
			}

			@Override
			public void setTransferHandler(TransferHandler newHandler)
			{
				if (table != null)
					table.setTransferHandler(newHandler);
				else super.setTransferHandler(newHandler);
			}

			@Override
			public boolean getDragEnabled()
			{
				if (table != null)
					return table.getDragEnabled();
				else return super.getDragEnabled();
			}

			@Override
			public void setDragEnabled(boolean b)
			{
				if (table != null)
					table.setDragEnabled(b);
				else super.setDragEnabled(b);
			}
		};

		fakeList.setSelectionModel(listSelectionModel);
		detailsTable.addMouseListener(createDoubleClickListener(chooser,
				fakeList));
		// detailsTable.addMouseListener(createSingleClickListener(chooser,
		// fakeList));

		JScrollPane scrollpane = new JScrollPane(detailsTable);
		scrollpane.setComponentOrientation(chooser.getComponentOrientation());
		LookAndFeel.installColors(scrollpane.getViewport(), "Table.background",
				"Table.foreground");

		// Adjust width of first column so the table fills the viewport when
		// first displayed (temporary listener).
		scrollpane.addComponentListener(new ComponentAdapter()
		{
			@Override
			public void componentResized(ComponentEvent e)
			{
				JScrollPane sp = (JScrollPane) e.getComponent();
				fixNameColumnWidth(sp.getViewport().getSize().width);
				sp.removeComponentListener(this);
			}
		});

		p.add(scrollpane, BorderLayout.CENTER);
		return p;
	}

	private void fixNameColumnWidth(int viewWidth)
	{
		TableColumn nameCol = detailsTable.getColumnModel().getColumn(
				COLUMN_FILENAME);
		int tableWidth = detailsTable.getPreferredSize().width;

		if (tableWidth < viewWidth)
			nameCol.setPreferredWidth(nameCol.getPreferredWidth() + viewWidth
					- tableWidth);
	}

	private class DelayedSelectionUpdater implements Runnable
	{
		DelayedSelectionUpdater()
		{
			SwingUtilities.invokeLater(this);
		}

		public void run()
		{
			setFileSelected();
		}
	}

	/**
	 * Creates a selection listener for the list of files and directories.
	 * 
	 * @param fc
	 *            a <code>JFileChooser</code>
	 * @return a <code>ListSelectionListener</code>
	 */
	@Override
	public ListSelectionListener createListSelectionListener(JFileChooser fc)
	{
		return new SelectionListener()
		{
			@Override
			public void valueChanged(ListSelectionEvent e)
			{
				if (!e.getValueIsAdjusting())
				{
					JFileChooser chooser = getFileChooser();
					FileSystemView fsv = chooser.getFileSystemView();
					JList list = (JList) e.getSource();

					if (chooser.isMultiSelectionEnabled())
					{
						File[] files = null;
						Object[] objects = list.getSelectedValues();
						if (objects != null)
							if (( objects.length == 1 )
									&& ( (File) objects[0] ).isDirectory()
									&& chooser
											.isTraversable(( (File) objects[0] ))
									&& ( ( chooser.getFileSelectionMode() == JFileChooser.FILES_ONLY ) || !fsv
											.isFileSystem(( (File) objects[0] )) ))
							{
								setDirectorySelected(true);
								setDirectory(( (File) objects[0] ));
							}
							else
							{
								files = new File[objects.length];
								int j = 0;
								for (Object element : objects)
								{
									File f = (File) element;
									boolean isDir = f.isDirectory();
									boolean isFile = !isDir ? f.isFile()
											: !isDir;
									if (( chooser.isFileSelectionEnabled() && isFile )
											|| ( chooser
													.isDirectorySelectionEnabled()
													&& fsv.isFileSystem(f) && isDir ))
										files[j++] = f;
								}
								if (j == 0)
									files = null;
								else if (j < objects.length)
								{
									File[] tmpFiles = new File[j];
									System.arraycopy(files, 0, tmpFiles, 0, j);
									files = tmpFiles;
								}
								setDirectorySelected(false);
							}
						chooser.setSelectedFiles(files);
					}
					else
					{
						File file = (File) list.getSelectedValue();
						if (( file != null )
								&& file.isDirectory()
								&& chooser.isTraversable(file)
								&& ( ( chooser.getFileSelectionMode() == JFileChooser.FILES_ONLY ) || !fsv
										.isFileSystem(file) ))
						{
							setDirectorySelected(true);
							setDirectory(file);
							chooser.setSelectedFile(null);
						}
						else
						{
							setDirectorySelected(false);
							if (file != null) chooser.setSelectedFile(file);
						}
					}
				}
			}
		};
	}

	private MouseListener createSingleClickListener(JFileChooser fc, JList list)
	{
		return new SingleClickListener(list);
	}

	int lastIndex = -1;

	File editFile = null;

	int editX = 20;

	private int getEditIndex()
	{
		return lastIndex;
	}

	private void setEditIndex(int i)
	{
		lastIndex = i;
	}

	private void resetEditIndex()
	{
		lastIndex = -1;
	}

	private void cancelEdit()
	{
		if (editFile != null)
		{
			editFile = null;
			list.remove(editCell);
			centerPanel.repaint();
		}
		else if (( detailsTable != null ) && detailsTable.isEditing())
			detailsTable.getCellEditor().cancelCellEditing();
	}

	JTextField editCell = null;

	@SuppressWarnings( "deprecation" )
	private void editFileName(int index)
	{
		ensureIndexIsVisible(index);
		if (listViewPanel.isVisible())
		{
			editFile = (File) getModel().getElementAt(index);
			Rectangle r = list.getCellBounds(index, index);
			if (editCell == null)
			{
				editCell = new JTextField();
				editCell.addActionListener(new EditActionListener());
				editCell.addFocusListener(editorFocusListener);
				editCell.setNextFocusableComponent(list);
			}
			list.add(editCell);
			editCell.setText(getFileChooser().getName(editFile));
			if (list.getComponentOrientation().isLeftToRight())
				editCell.setBounds(editX + r.x, r.y, r.width - editX, r.height);
			else editCell.setBounds(r.x, r.y, r.width - editX, r.height);

			editCell.requestFocus();
			editCell.selectAll();
		}
		else if (detailsViewPanel.isVisible())
			detailsTable.editCellAt(index, COLUMN_FILENAME);
	}

	protected class SingleClickListener extends MouseAdapter
	{
		JList list;

		public SingleClickListener( JList list )
		{
			this.list = list;
		}

		@Override
		public void mouseClicked(MouseEvent e)
		{
			if (SwingUtilities.isLeftMouseButton(e))
				if (e.getClickCount() == 1)
				{
					JFileChooser fc = getFileChooser();
					int index = list.locationToIndex(e.getPoint());
					if (( !fc.isMultiSelectionEnabled() || ( fc
							.getSelectedFiles().length <= 1 ) )
							&& ( index >= 0 )
							&& list.isSelectedIndex(index)
							&& ( getEditIndex() == index )
							&& ( editFile == null ))
						editFileName(index);
					else if (index >= 0)
						setEditIndex(index);
					else resetEditIndex();
				}
				else // on double click (open or drill down one directory) be
				// sure to clear the edit index
				resetEditIndex();
		}
	}

	class EditActionListener implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			applyEdit();
		}
	}

	private void applyEdit()
	{
		if (( editFile != null ) && editFile.exists())
		{
			JFileChooser chooser = getFileChooser();
			String oldDisplayName = chooser.getName(editFile);
			String oldFileName = editFile.getName();
			String newDisplayName = editCell.getText().trim();
			String newFileName;

			if (!newDisplayName.equals(oldDisplayName))
			{
				newFileName = newDisplayName;
				// Check if extension is hidden from user
				int i1 = oldFileName.length();
				int i2 = oldDisplayName.length();
				if (( i1 > i2 ) && ( oldFileName.charAt(i2) == '.' ))
					newFileName = newDisplayName + oldFileName.substring(i2);

				// rename
				FileSystemView fsv = chooser.getFileSystemView();
				File f2 = fsv.createFileObject(editFile.getParentFile(),
						newFileName);
				if (!f2.exists() && getModel().renameFile(editFile, f2))
				{
					if (fsv.isParent(chooser.getCurrentDirectory(), f2))
					{
						if (chooser.isMultiSelectionEnabled())
							chooser.setSelectedFiles(new File[]
							{ f2 });
						else chooser.setSelectedFile(f2);
					}
					else
					{
						// Could be because of delay in updating Desktop folder
						// chooser.setSelectedFile(null);
					}
				}
				else
				{
					// PENDING(jeff) - show a dialog indicating failure
				}
			}
		}
		if (( detailsTable != null ) && detailsTable.isEditing())
			detailsTable.getCellEditor().stopCellEditing();
		cancelEdit();
	}

	protected class FileRenderer extends DefaultListCellRenderer
	{

		/**
		 * 
		 */
		private static final long serialVersionUID = 9097009812546913947L;

		@Override
		public Component getListCellRendererComponent(JList list, Object value,
				int index, boolean isSelected, boolean cellHasFocus)
		{

			super.getListCellRendererComponent(list, value, index, isSelected,
					cellHasFocus);
			File file = (File) value;
			String fileName = getFileChooser().getName(file);
			setText(fileName);

			Icon icon = getFileChooser().getIcon(file);
			setIcon(icon);

			if (isSelected) // PENDING(jeff) - grab padding (4) below from
				// defaults table.
				editX = icon.getIconWidth() + 4;

			return this;
		}
	}

	@Override
	public void uninstallUI(JComponent c)
	{
		// Remove listeners
		c.removePropertyChangeListener(filterComboBoxModel);
		cancelButton.removeActionListener(getCancelSelectionAction());
		approveButton.removeActionListener(getApproveSelectionAction());
		fileNameTextField.removeActionListener(getApproveSelectionAction());

		super.uninstallUI(c);
	}

	/**
	 * Returns the preferred size of the specified <code>JFileChooser</code>.
	 * The preferred size is at least as large, in both height and width, as the
	 * preferred size recommended by the file chooser's layout manager.
	 * 
	 * @param c
	 *            a <code>JFileChooser</code>
	 * @return a <code>Dimension</code> specifying the preferred width and
	 *         height of the file chooser
	 */
	@Override
	public Dimension getPreferredSize(JComponent c)
	{
		int prefWidth = PREF_SIZE.width;
		Dimension d = c.getLayout().preferredLayoutSize(c);
		if (d != null)
			return new Dimension(d.width < prefWidth ? prefWidth : d.width,
					d.height < PREF_SIZE.height ? PREF_SIZE.height : d.height);
		else return new Dimension(prefWidth, PREF_SIZE.height);
	}

	/**
	 * Returns the minimum size of the <code>JFileChooser</code>.
	 * 
	 * @param c
	 *            a <code>JFileChooser</code>
	 * @return a <code>Dimension</code> specifying the minimum width and
	 *         height of the file chooser
	 */
	@Override
	public Dimension getMinimumSize(JComponent c)
	{
		return MIN_SIZE;
	}

	/**
	 * Returns the maximum size of the <code>JFileChooser</code>.
	 * 
	 * @param c
	 *            a <code>JFileChooser</code>
	 * @return a <code>Dimension</code> specifying the maximum width and
	 *         height of the file chooser
	 */
	@Override
	public Dimension getMaximumSize(JComponent c)
	{
		return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
	}

	void setFileSelected()
	{
		if (getFileChooser().isMultiSelectionEnabled()
				&& !isDirectorySelected())
		{
			File[] files = getFileChooser().getSelectedFiles(); // Should be
			// selected
			Object[] selectedObjects = list.getSelectedValues(); // Are
			// actually
			// selected

			if (true)
			{
				for (Object element : selectedObjects)
				{
					boolean found = false;
					for (File element0 : files)
						if (element0.equals(element))
						{
							found = true;
							break;
						}
					if (!found)
					{
						int index = getModel().indexOf(element);
						if (index >= 0)
							listSelectionModel.removeSelectionInterval(index,
									index);
					}
				}
				for (File element : files)
				{
					boolean found = false;
					for (Object element0 : selectedObjects)
						if (element.equals(element0))
						{
							found = true;
							break;
						}
					if (!found)
					{
						int index = getModel().indexOf(element);
						if (index >= 0)
							listSelectionModel.addSelectionInterval(index,
									index);
					}
				}
			}
			else
			{
				listSelectionModel.setValueIsAdjusting(true);
				try
				{
					Arrays.sort(files);
					Arrays.sort(selectedObjects);

					int shouldIndex = 0;
					int actuallyIndex = 0;

					// Remove files that shouldn't be selected and add files
					// which should be selected
					// Note: Assume files are already sorted in compareTo order.
					while (( shouldIndex < files.length )
							&& ( actuallyIndex < selectedObjects.length ))
					{
						int comparison = files[shouldIndex]
								.compareTo((File) selectedObjects[actuallyIndex]);
						if (comparison < 0)
						{
							int index = getModel().indexOf(files[shouldIndex]);
							listSelectionModel.addSelectionInterval(index,
									index);
							shouldIndex++;
						}
						else if (comparison > 0)
						{
							int index = getModel().indexOf(
									selectedObjects[actuallyIndex]);
							listSelectionModel.removeSelectionInterval(index,
									index);
							actuallyIndex++;
						}
						else
						{
							// Do nothing
							shouldIndex++;
							actuallyIndex++;
						}

					}

					while (shouldIndex < files.length)
					{
						int index = getModel().indexOf(files[shouldIndex]);
						listSelectionModel.addSelectionInterval(index, index);
						shouldIndex++;
					}

					while (actuallyIndex < selectedObjects.length)
					{
						int index = getModel().indexOf(
								selectedObjects[actuallyIndex]);
						listSelectionModel
								.removeSelectionInterval(index, index);
						actuallyIndex++;
					}
				}
				finally
				{
					listSelectionModel.setValueIsAdjusting(false);
				}
			}
		}
		else
		{
			JFileChooser chooser = getFileChooser();
			File f = null;
			if (isDirectorySelected())
				f = getDirectory();
			else f = chooser.getSelectedFile();
			int i;
			if (( f != null ) && ( ( i = getModel().indexOf(f) ) >= 0 ))
			{
				listSelectionModel.setSelectionInterval(i, i);
				ensureIndexIsVisible(i);
			}
			else listSelectionModel.clearSelection();
		}
	}

	private String fileNameString(File file)
	{
		if (file == null)
			return null;
		else
		{
			JFileChooser fc = getFileChooser();
			if (fc.isDirectorySelectionEnabled()
					&& !fc.isFileSelectionEnabled())
				return file.getPath();
			else return file.getName();
		}
	}

	private String fileNameString(File[] files)
	{
		StringBuffer buf = new StringBuffer();
		for (int i = 0; ( files != null ) && ( i < files.length ); i++)
		{
			if (i > 0) buf.append(" ");
			if (files.length > 1) buf.append("\"");
			buf.append(fileNameString(files[i]));
			if (files.length > 1) buf.append("\"");
		}
		return buf.toString();
	}

	/* The following methods are used by the PropertyChange Listener */

	private void doSelectedFileChanged(PropertyChangeEvent e)
	{
		applyEdit();
		File f = (File) e.getNewValue();
		JFileChooser fc = getFileChooser();
		if (( f != null )
				&& ( ( fc.isFileSelectionEnabled() && !f.isDirectory() ) || ( f
						.isDirectory() && fc.isDirectorySelectionEnabled() ) ))
		{

			setFileName(fileNameString(f));
			setFileSelected();
		}
	}

	private void doSelectedFilesChanged(PropertyChangeEvent e)
	{
		applyEdit();
		File[] files = (File[]) e.getNewValue();
		JFileChooser fc = getFileChooser();
		if (( files != null )
				&& ( files.length > 0 )
				&& ( ( files.length > 1 ) || fc.isDirectorySelectionEnabled() || !files[0]
						.isDirectory() ))
		{
			setFileName(fileNameString(files));
			setFileSelected();
		}
	}

	private void doDirectoryChanged(PropertyChangeEvent e)
	{
		JFileChooser fc = getFileChooser();
		FileSystemView fsv = fc.getFileSystemView();

		applyEdit();
		resetEditIndex();
		clearIconCache();
		listSelectionModel.clearSelection();
		ensureIndexIsVisible(0);
		File currentDirectory = fc.getCurrentDirectory();
		if (currentDirectory != null)
		{
			directoryComboBoxModel.addItem(currentDirectory);
			getNewFolderAction().setEnabled(currentDirectory.canWrite());
			getChangeToParentDirectoryAction().setEnabled(
					!fsv.isRoot(currentDirectory));

			if (fc.isDirectorySelectionEnabled()
					&& !fc.isFileSelectionEnabled())
				if (fsv.isFileSystem(currentDirectory))
					setFileName(currentDirectory.getPath());
				else setFileName(null);
		}
	}

	private void doFilterChanged(PropertyChangeEvent e)
	{
		applyEdit();
		resetEditIndex();
		clearIconCache();
		listSelectionModel.clearSelection();
	}

	private void doFileSelectionModeChanged(PropertyChangeEvent e)
	{
		applyEdit();
		resetEditIndex();
		clearIconCache();
		listSelectionModel.clearSelection();

		JFileChooser fc = getFileChooser();
		File currentDirectory = fc.getCurrentDirectory();
		if (( currentDirectory != null ) && fc.isDirectorySelectionEnabled()
				&& !fc.isFileSelectionEnabled()
				&& fc.getFileSystemView().isFileSystem(currentDirectory))
			setFileName(currentDirectory.getPath());
		else setFileName(null);
	}

	private void doMultiSelectionChanged(PropertyChangeEvent e)
	{
		if (getFileChooser().isMultiSelectionEnabled())
			listSelectionModel
					.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
		else
		{
			listSelectionModel
					.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			listSelectionModel.clearSelection();
			getFileChooser().setSelectedFiles(null);
		}
	}

	private void doAccessoryChanged(PropertyChangeEvent e)
	{
		if (getAccessoryPanel() != null)
		{
			if (e.getOldValue() != null)
				getAccessoryPanel().remove((JComponent) e.getOldValue());
			JComponent accessory = (JComponent) e.getNewValue();
			if (accessory != null)
				getAccessoryPanel().add(accessory, BorderLayout.CENTER);
		}
	}

	private void doApproveButtonTextChanged(PropertyChangeEvent e)
	{
		JFileChooser chooser = getFileChooser();
		approveButton.setText(getApproveButtonText(chooser));
		approveButton.setToolTipText(getApproveButtonToolTipText(chooser));
	}

	private void doDialogTypeChanged(PropertyChangeEvent e)
	{
		JFileChooser chooser = getFileChooser();
		approveButton.setText(getApproveButtonText(chooser));
		approveButton.setToolTipText(getApproveButtonToolTipText(chooser));
		if (chooser.getDialogType() == JFileChooser.SAVE_DIALOG)
			lookInLabel.setText(saveInLabelText);
		else lookInLabel.setText(lookInLabelText);
	}

	private void doApproveButtonMnemonicChanged(PropertyChangeEvent e)
	{
		// Note: Metal does not use mnemonics for approve and cancel
	}

	private void doControlButtonsChanged(PropertyChangeEvent e)
	{
		if (getFileChooser().getControlButtonsAreShown())
			addControlButtons();
		else removeControlButtons();
	}

	/*
	 * Listen for filechooser property changes, such as the selected file
	 * changing, or the type of the dialog changing.
	 */
	@Override
	public PropertyChangeListener createPropertyChangeListener(JFileChooser fc)
	{
		return new PropertyChangeListener()
		{
			public void propertyChange(PropertyChangeEvent e)
			{
				String s = e.getPropertyName();
				if (s.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY))
					doSelectedFileChanged(e);
				else if (s.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY))
					doSelectedFilesChanged(e);
				else if (s.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY))
					doDirectoryChanged(e);
				else if (s.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY))
					doFilterChanged(e);
				else if (s
						.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY))
					doFileSelectionModeChanged(e);
				else if (s
						.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY))
					doMultiSelectionChanged(e);
				else if (s.equals(JFileChooser.ACCESSORY_CHANGED_PROPERTY))
					doAccessoryChanged(e);
				else if (s
						.equals(JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY)
						|| s
								.equals(JFileChooser.APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY))
					doApproveButtonTextChanged(e);
				else if (s.equals(JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY))
					doDialogTypeChanged(e);
				else if (s
						.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY))
					doApproveButtonMnemonicChanged(e);
				else if (s
						.equals(JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY))
					doControlButtonsChanged(e);
				else if (s.equals(JFileChooser.CANCEL_SELECTION))
					applyEdit();
				else if (s.equals("componentOrientation"))
				{
					ComponentOrientation o = (ComponentOrientation) e
							.getNewValue();
					JFileChooser cc = (JFileChooser) e.getSource();
					if (o != (ComponentOrientation) e.getOldValue())
						cc.applyComponentOrientation(o);
					if (detailsTable != null)
					{
						detailsTable.setComponentOrientation(o);
						detailsTable.getParent().getParent()
								.setComponentOrientation(o);
					}
				}
				else if (s.equals("ancestor"))
					if (( e.getOldValue() == null )
							&& ( e.getNewValue() != null ))
					{
						// Ancestor was added, set initial focus
						fileNameTextField.selectAll();
						fileNameTextField.requestFocus();
					}
			}
		};
	}

	protected void removeControlButtons()
	{
		getBottomPanel().remove(getButtonPanel());
	}

	protected void addControlButtons()
	{
		getBottomPanel().add(getButtonPanel());
	}

	private void ensureIndexIsVisible(int i)
	{
		if (i >= 0)
		{
			list.ensureIndexIsVisible(i);
			if (detailsTable != null)
				detailsTable.scrollRectToVisible(detailsTable.getCellRect(i,
						COLUMN_FILENAME, true));
		}
	}

	@Override
	public void ensureFileIsVisible(JFileChooser fc, File f)
	{
		ensureIndexIsVisible(getModel().indexOf(f));
	}

	@Override
	public void rescanCurrentDirectory(JFileChooser fc)
	{
		getModel().validateFileCache();
	}

	@Override
	public String getFileName()
	{
		if (fileNameTextField != null)
			return fileNameTextField.getText();
		else return null;
	}

	@Override
	public void setFileName(String filename)
	{
		if (fileNameTextField != null) fileNameTextField.setText(filename);
	}

	/**
	 * Property to remember whether a directory is currently selected in the UI.
	 * This is normally called by the UI on a selection event.
	 * 
	 * @param directorySelected
	 *            if a directory is currently selected.
	 * @since 1.4
	 */
	@Override
	protected void setDirectorySelected(boolean directorySelected)
	{
		super.setDirectorySelected(directorySelected);
		JFileChooser chooser = getFileChooser();
		if (directorySelected)
		{
			approveButton.setText(directoryOpenButtonText);
			approveButton.setToolTipText(directoryOpenButtonToolTipText);
		}
		else
		{
			approveButton.setText(getApproveButtonText(chooser));
			approveButton.setToolTipText(getApproveButtonToolTipText(chooser));
		}
	}

	@Override
	public String getDirectoryName()
	{
		// PENDING(jeff) - get the name from the directory combobox
		return null;
	}

	@Override
	public void setDirectoryName(String dirname)
	{
		// PENDING(jeff) - set the name in the directory combobox
	}

	protected DirectoryComboBoxRenderer createDirectoryComboBoxRenderer(
			JFileChooser fc)
	{
		return new DirectoryComboBoxRenderer();
	}

	//
	// Renderer for DirectoryComboBox
	//
	class DirectoryComboBoxRenderer extends DefaultListCellRenderer
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = 6539726767400519342L;

		IndentIcon ii = new IndentIcon();

		@Override
		public Component getListCellRendererComponent(JList list, Object value,
				int index, boolean isSelected, boolean cellHasFocus)
		{

			super.getListCellRendererComponent(list, value, index, isSelected,
					cellHasFocus);

			if (value == null)
			{
				setText("");
				return this;
			}
			File directory = (File) value;
			setText(getFileChooser().getName(directory));
			Icon icon = getFileChooser().getIcon(directory);
			ii.icon = icon;
			ii.depth = directoryComboBoxModel.getDepth(index);
			setIcon(ii);

			return this;
		}
	}

	final static int space = 10;

	class IndentIcon implements Icon
	{

		Icon icon = null;

		int depth = 0;

		public void paintIcon(Component c, Graphics g, int x, int y)
		{
			if (c.getComponentOrientation().isLeftToRight())
				icon.paintIcon(c, g, x + depth * space, y);
			else icon.paintIcon(c, g, x, y);
		}

		public int getIconWidth()
		{
			return icon.getIconWidth() + depth * space;
		}

		public int getIconHeight()
		{
			return icon.getIconHeight();
		}

	}

	//
	// DataModel for DirectoryComboxbox
	//
	protected DirectoryComboBoxModel createDirectoryComboBoxModel(
			JFileChooser fc)
	{
		return new DirectoryComboBoxModel();
	}

	/**
	 * Data model for a type-face selection combo-box.
	 */
	@SuppressWarnings( "unchecked" )
	protected class DirectoryComboBoxModel extends AbstractListModel implements
			ComboBoxModel
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = -4353065126403732731L;

		Vector directories = new Vector();

		int[] depths = null;

		File selectedDirectory = null;

		JFileChooser chooser = getFileChooser();

		FileSystemView fsv = chooser.getFileSystemView();

		public DirectoryComboBoxModel()
		{
			// Add the current directory to the model, and make it the
			// selectedDirectory
			File dir = getFileChooser().getCurrentDirectory();
			if (dir != null) addItem(dir);
		}

		/**
		 * Adds the directory to the model and sets it to be selected,
		 * additionally clears out the previous selected directory and the paths
		 * leading up to it, if any.
		 */
		private void addItem(File directory)
		{

			if (directory == null) return;

			directories.clear();

			File[] baseFolders;
			if (useShellFolder)
				baseFolders = (File[]) ShellFolder
						.get("fileChooserComboBoxFolders");
			else baseFolders = fsv.getRoots();
			directories.addAll(Arrays.asList(baseFolders));

			// Get the canonical (full) path. This has the side
			// benefit of removing extraneous chars from the path,
			// for example /foo/bar/ becomes /foo/bar
			File canonical = null;
			try
			{
				canonical = directory.getCanonicalFile();
			}
			catch (IOException e)
			{
				// Maybe drive is not ready. Can't abort here.
				canonical = directory;
			}

			// create File instances of each directory leading up to the top
			try
			{
				File sf = ShellFolder.getShellFolder(canonical);
				File f = sf;
				Vector path = new Vector(10);
				do
					path.addElement(f);
				while (( f = f.getParentFile() ) != null);

				int pathCount = path.size();
				// Insert chain at appropriate place in vector
				for (int i = 0; i < pathCount; i++)
				{
					f = (File) path.get(i);
					if (directories.contains(f))
					{
						int topIndex = directories.indexOf(f);
						for (int j = i - 1; j >= 0; j--)
							directories.insertElementAt(path.get(j), topIndex
									+ i - j);
						break;
					}
				}
				calculateDepths();
				setSelectedItem(sf);
			}
			catch (FileNotFoundException ex)
			{
				calculateDepths();
			}
		}

		private void calculateDepths()
		{
			depths = new int[directories.size()];
			for (int i = 0; i < depths.length; i++)
			{
				File dir = (File) directories.get(i);
				File parent = dir.getParentFile();
				depths[i] = 0;
				if (parent != null) for (int j = i - 1; j >= 0; j--)
					if (parent.equals(directories.get(j)))
					{
						depths[i] = depths[j] + 1;
						break;
					}
			}
		}

		public int getDepth(int i)
		{
			return ( ( depths != null ) && ( i >= 0 ) && ( i < depths.length ) ) ? depths[i]
					: 0;
		}

		public void setSelectedItem(Object selectedDirectory)
		{
			this.selectedDirectory = (File) selectedDirectory;
			fireContentsChanged(this, -1, -1);
		}

		public Object getSelectedItem()
		{
			return selectedDirectory;
		}

		public int getSize()
		{
			return directories.size();
		}

		public Object getElementAt(int index)
		{
			return directories.elementAt(index);
		}
	}

	//
	// Renderer for Types ComboBox
	//
	protected FilterComboBoxRenderer createFilterComboBoxRenderer()
	{
		return new FilterComboBoxRenderer();
	}

	/**
	 * Render different type sizes and styles.
	 */
	public class FilterComboBoxRenderer extends DefaultListCellRenderer
	{
		private static final long serialVersionUID = -6488773660432125813L;

		@Override
		public Component getListCellRendererComponent(JList list, Object value,
				int index, boolean isSelected, boolean cellHasFocus)
		{

			super.getListCellRendererComponent(list, value, index, isSelected,
					cellHasFocus);

			if (( value != null ) && ( value instanceof FileFilter ))
				setText(( (FileFilter) value ).getDescription());

			return this;
		}
	}

	//
	// DataModel for Types Comboxbox
	//
	protected FilterComboBoxModel createFilterComboBoxModel()
	{
		return new FilterComboBoxModel();
	}

	/**
	 * Data model for a type-face selection combo-box.
	 */
	protected class FilterComboBoxModel extends AbstractListModel implements
			ComboBoxModel, PropertyChangeListener
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = 1114455637205109492L;

		protected FileFilter[] filters;

		protected FilterComboBoxModel()
		{
			super();
			filters = getFileChooser().getChoosableFileFilters();
		}

		public void propertyChange(PropertyChangeEvent e)
		{
			String prop = e.getPropertyName();
			if (prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY)
			{
				filters = (FileFilter[]) e.getNewValue();
				fireContentsChanged(this, -1, -1);
			}
			else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY)
				fireContentsChanged(this, -1, -1);
		}

		public void setSelectedItem(Object filter)
		{
			if (filter != null)
			{
				getFileChooser().setFileFilter((FileFilter) filter);
				setFileName(null);
				fireContentsChanged(this, -1, -1);
			}
		}

		public Object getSelectedItem()
		{
			// Ensure that the current filter is in the list.
			// NOTE: we shouldnt' have to do this, since JFileChooser adds
			// the filter to the choosable filters list when the filter
			// is set. Lets be paranoid just in case someone overrides
			// setFileFilter in JFileChooser.
			FileFilter currentFilter = getFileChooser().getFileFilter();
			boolean found = false;
			if (currentFilter != null)
			{
				for (FileFilter element : filters)
					if (element == currentFilter) found = true;
				if (found == false)
					getFileChooser().addChoosableFileFilter(currentFilter);
			}
			return getFileChooser().getFileFilter();
		}

		public int getSize()
		{
			if (filters != null)
				return filters.length;
			else return 0;
		}

		public Object getElementAt(int index)
		{
			if (index > getSize() - 1) // This shouldn't happen. Try to recover
				// gracefully.
				return getFileChooser().getFileFilter();
			if (filters != null)
				return filters[index];
			else return null;
		}
	}

	public void valueChanged(ListSelectionEvent e)
	{
		JFileChooser fc = getFileChooser();
		File f = fc.getSelectedFile();
		if (!e.getValueIsAdjusting() && ( f != null )
				&& !getFileChooser().isTraversable(f))
			setFileName(fileNameString(f));
	}

	/**
	 * Acts when DirectoryComboBox has changed the selected item.
	 */
	protected class DirectoryComboBoxAction extends AbstractAction
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = -7476625032581163730L;

		protected DirectoryComboBoxAction()
		{
			super("DirectoryComboBoxAction");
		}

		public void actionPerformed(ActionEvent e)
		{
			File f = (File) directoryComboBox.getSelectedItem();
			getFileChooser().setCurrentDirectory(f);
		}
	}

	@Override
	protected JButton getApproveButton(JFileChooser fc)
	{
		return approveButton;
	}

	/**
	 * <code>ButtonAreaLayout</code> behaves in a similar manner to
	 * <code>FlowLayout</code>. It lays out all components from left to
	 * right, flushed right. The widths of all components will be set to the
	 * largest preferred size width.
	 */
	private static class ButtonAreaLayout implements LayoutManager
	{
		private int hGap = 5;

		private int topMargin = 17;

		public void addLayoutComponent(String string, Component comp)
		{
		}

		public void layoutContainer(Container container)
		{
			Component[] children = container.getComponents();

			if (( children != null ) && ( children.length > 0 ))
			{
				int numChildren = children.length;
				Dimension[] sizes = new Dimension[numChildren];
				Insets insets = container.getInsets();
				int yLocation = insets.top + topMargin;
				int maxWidth = 0;

				for (int counter = 0; counter < numChildren; counter++)
				{
					sizes[counter] = children[counter].getPreferredSize();
					maxWidth = Math.max(maxWidth, sizes[counter].width);
				}
				int xLocation, xOffset;
				if (container.getComponentOrientation().isLeftToRight())
				{
					xLocation = container.getSize().width - insets.left
							- maxWidth;
					xOffset = hGap + maxWidth;
				}
				else
				{
					xLocation = insets.left;
					xOffset = -( hGap + maxWidth );
				}
				for (int counter = numChildren - 1; counter >= 0; counter--)
				{
					children[counter].setBounds(xLocation, yLocation, maxWidth,
							sizes[counter].height);
					xLocation -= xOffset;
				}
			}
		}

		public Dimension minimumLayoutSize(Container c)
		{
			if (c != null)
			{
				Component[] children = c.getComponents();

				if (( children != null ) && ( children.length > 0 ))
				{
					int numChildren = children.length;
					int height = 0;
					Insets cInsets = c.getInsets();
					int extraHeight = topMargin + cInsets.top + cInsets.bottom;
					int extraWidth = cInsets.left + cInsets.right;
					int maxWidth = 0;

					for (int counter = 0; counter < numChildren; counter++)
					{
						Dimension aSize = children[counter].getPreferredSize();
						height = Math.max(height, aSize.height);
						maxWidth = Math.max(maxWidth, aSize.width);
					}
					return new Dimension(extraWidth + numChildren * maxWidth
							+ ( numChildren - 1 ) * hGap, extraHeight + height);
				}
			}
			return new Dimension(0, 0);
		}

		public Dimension preferredLayoutSize(Container c)
		{
			return minimumLayoutSize(c);
		}

		public void removeLayoutComponent(Component c)
		{
		}
	}

	private static void groupLabels(AlignedLabel[] group)
	{
		for (AlignedLabel element : group)
			element.group = group;
	}

	private class AlignedLabel extends JLabel
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = 2834197648367470182L;

		private AlignedLabel[] group;

		private int maxWidth = 0;

		AlignedLabel( String text )
		{
			super(text);
			setAlignmentX(Component.LEFT_ALIGNMENT);
		}

		@Override
		public Dimension getPreferredSize()
		{
			Dimension d = super.getPreferredSize();
			// Align the width with all other labels in group.
			return new Dimension(getMaxWidth() + 11, d.height);
		}

		private int getMaxWidth()
		{
			if (( maxWidth == 0 ) && ( group != null ))
			{
				int max = 0;
				for (AlignedLabel element : group)
					max = Math.max(element.getSuperPreferredWidth(), max);
				for (AlignedLabel element : group)
					element.maxWidth = max;
			}
			return maxWidth;
		}

		private int getSuperPreferredWidth()
		{
			return super.getPreferredSize().width;
		}
	}
}
